package com.strato.osgi.repository.v1_0_0.impl;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;

import com.strato.base.osgi.v1_0_0.BundleUtil;
import com.strato.osgi.repository.v1_0_0.BundleMeta;
import com.strato.osgi.repository.v1_0_0.Repository;
import com.strato.osgi.repository.v1_0_0.activator.Activator;
import com.strato.osgi.repository.v1_0_0.manifest.ManifestParser;
import com.strato.osgi.repository.v1_0_0.manifest.ManifestParser.ParsedHeaderClause;
import com.strato.osgi.repository.v1_0_0.manifest.Version;
import com.strato.osgi.repository.v1_0_0.manifest.VersionRange;

public class FileSystemRepository implements Repository {
	
	private Map<String,Node> nodes=new HashMap<String,Node>();
	
	private Map<Description,Node> cache=new HashMap<Description,Node>();
	
	private Map<String,String> filters=null;
	
	private String rootDir;

	public FileSystemRepository(String rootDir){
		this.rootDir=rootDir;
	}

	public String getRootDir() {
		return rootDir;
	}

	public void setRootDir(String rootDir) {
		this.rootDir = rootDir;
	}
	
	public void rescan(){
		nodes.clear();
		cache.clear();
		File file=new File(rootDir);
		scan(file);
		relate();
	}

	private void relate() {
		for(Iterator<Node> nodeIt = nodes.values().iterator();nodeIt.hasNext();){
			Node n = nodeIt.next();
			Set<Description> imports = n.imports;
			for(Iterator<Description> importsIt = imports.iterator();importsIt.hasNext();){
				Description imp = importsIt.next();
				Node require = find(imp);
				if(require!=null){
					n.dependencies.add(require);
				}
			}
			n.imports = null;
		}
	}


	private Node find(Description imp) {
		Node node = null;
		
		node = (Node) cache.get(imp);
		if(node!=null){
			return node;
		}
		
		if(filters!=null){
			String v = (String) filters.get(imp.name);
			if(v!=null && in(imp.version,v)){
				return null;
			}
		}
		
		for(Iterator<Node> nodeIt = nodes.values().iterator();nodeIt.hasNext();){
			Node n = nodeIt.next();
			Set<Description> exports = n.exports;
			for(Iterator<Description> exportsIt = exports.iterator();exportsIt.hasNext();){
				Description exp = exportsIt.next();
				if(exp.name.equals(imp.name) && in(imp.version,exp.version)){
					node = n;
					cache.put(imp, n);
					exportsIt.remove();
					break;
				}
			}
		}
		return node;
	}


	private boolean in(String need, String supply) {
		if("*".equals(need) || "*".equals(supply) ||  need.equals(supply)){
			return true;
		}
		VersionRange vr = VersionRange.parse(need);
		Version v = new Version(supply);
		return vr.isInRange(v);
	}

	private void scan(File file) {
		if(file.isDirectory()){
			File[] children = file.listFiles();
			if(children!=null){
				for(File child : children){
					scan(child);
				}
			}
		}else{
			Node node = checkup(file);
			if(node!=null){
				nodes.put(join(node), node);	
			}
		}
	}

	private Node checkup(File file) {
		File root=new File(rootDir);
		Node node = null;
		if(!file.getName().endsWith(".jar")){
			return node;
		}
		JarFile jar=null;
		try {
			jar = new JarFile(file);
			Manifest manifest = jar.getManifest();
			manifest.getEntries();
			Attributes attrs = manifest.getMainAttributes();
			String name = attrs.getValue("Bundle-SymbolicName");
			if(name==null || name.isEmpty()){
				return null;
			}
			node = new Node();
			node.name = name;
			node.version = attrs.getValue("Bundle-Version");
			String path = file.getCanonicalPath();
			path = path.substring(root.getCanonicalPath().length(),path.length());
			path = "." + path.replaceAll("\\\\","/");
			node.path = path;
			node.dependencies = new HashSet<Node>();
			node.exports = new HashSet<Description>();
			node.imports = new HashSet<Description>();
			
			//handle require bundle
			String requireBundle = attrs.getValue("Require-Bundle");
			handle(requireBundle,node.dependencies);
			
			//repord import packge
			String importPackge = attrs.getValue("Import-Package");
			handle(importPackge,node.imports);
			
			//record export package
			String exportPackage = attrs.getValue("Export-Package");
			handle(exportPackage,node.exports);
			
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(jar!=null){
				try {
					jar.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return node;
	}


	@SuppressWarnings({ "rawtypes", "unchecked" })
	private void handle(String source,Set set) {
		if(source==null || source.isEmpty()){
			return;
		}
		List<ParsedHeaderClause> header = ManifestParser.parseStandardHeader(source);
		for(Iterator<ParsedHeaderClause> it = header.iterator();it.hasNext();){
			ParsedHeaderClause clause = it.next();
		
			Description desc = new Description();
			List<String> path = clause.m_paths;
			if(path == null || path.size()==0){
				continue;
			}
			desc.name = path.get(0);
			Map<String,Object> attrs = clause.m_attrs;
			if(attrs == null || attrs.size()==0){
				desc.version = "*";
			}else{
				String version = (String) attrs.get("version");
				if(version==null){
					desc.version = "*";
				}else{
					desc.version = version;
				}
			}
			Map<String,String> dirs = clause.m_dirs;
			if(dirs!=null && "optional".equals(dirs.get("resolution"))){
				desc.ext.put("resolution", "optional");
			}
			set.add(desc);
		}
	}
	
	private String join(Description desc){
		return desc.name +":" + desc.version;
	}
	
	public class Description{
		String name;
		String version;
		Map<String,String> ext = new HashMap<String,String>();
		
		@Override
		public String toString() {
			return name+"("+version+")";
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getVersion() {
			return version;
		}

		public void setVersion(String version) {
			this.version = version;
		}

		public Map<String, String> getExt() {
			return ext;
		}

		public void setExt(Map<String, String> ext) {
			this.ext = ext;
		}

		public int hashCode() {
			int result = 17;
			result = 37 * result + name.hashCode();
			result = 37 * result + version.hashCode();
			return result;
		}
		
		public boolean equals(Object obj) {
			if(obj != null && obj instanceof Description){
				Description desc = (Description) obj;
				return name.equals(desc.name) && version.equals(desc.version);
			}
			return false;
		}
	}
	
	public class Node extends Description{
		String path;
		Set<Node> dependencies;
		Set<Description> exports;
		Set<Description> imports;
		public String getPath() {
			return path;
		}
		public Set<Node> getDependencies() {
			return dependencies;
		}
		public void setDependencies(Set<Node> dependencies) {
			this.dependencies = dependencies;
		}
		public Set<Description> getExports() {
			return exports;
		}
		public void setExports(Set<Description> exports) {
			this.exports = exports;
		}
		public Set<Description> getImports() {
			return imports;
		}
		public void setImports(Set<Description> imports) {
			this.imports = imports;
		}
		public void setPath(String path) {
			this.path = path;
		}
		
	}

	public class BundleMetaImpl implements BundleMeta{

		private Node node;

		public BundleMetaImpl(Node node){
			this.node=node;
		}
		
		@Override
		public String getPath() {
			if(rootDir.startsWith("/")){
				return "file:"+rootDir+node.getPath().substring(1);
			}else{
				return "file:/"+rootDir+node.getPath().substring(1);
			}
		}

		@Override
		public String getSymbolicName() {
			return node.getName();
		}

		@Override
		public String getVersion() {
			return node.getVersion();
		}

		@Override
		public String toString() {
			return getPath();
		}
		
	}
	
	public class BundleMetaComparator implements Comparator<BundleMeta>{
		@Override
		public int compare(BundleMeta o1, BundleMeta o2) {
			BundleMetaImpl m1=(BundleMetaImpl) o1;
			BundleMetaImpl m2=(BundleMetaImpl) o2;
			if(isDependent(m1,m2)){
				return -1;
			}else if(isDependent(m2,m1)){
				return 1;
			}
			return o1.getSymbolicName().compareTo(o2.getSymbolicName());
		}

		private boolean isDependent(BundleMetaImpl m1, BundleMetaImpl m2) {
			Set<Node> dependencies = m1.node.getDependencies();
			Node node = m2.node;
			boolean b=false;
			for(Description desc:dependencies){
				if(desc.getName().equals(node.getName())&&in(node.getVersion(), desc.getVersion())){
					b=true;
					break;
				}
			}
			return b;
		}
	}
	
	public BundleMeta getBundle(String symbolicName, String version) {
		Description desc=new Description();
		desc.name=symbolicName;
		desc.version=version;
		Node node = nodes.get(join(desc));
		if(node==null){
			return null;
		}
		return new BundleMetaImpl(node);
	}

	@Override
	public List<BundleMeta> getBundles(){
		List<BundleMeta> bundles=new ArrayList<BundleMeta>();
		for(Node node:nodes.values()){
			bundles.add(new BundleMetaImpl(node));
		}
		Collections.sort(bundles,new BundleMetaComparator());
		return bundles;
	}

	@Override
	public List<BundleMeta> getDependencies(BundleMeta meta){
		Description desc=new Description();
		desc.name=meta.getSymbolicName();
		desc.version=meta.getVersion();
		Node node = nodes.get(join(desc));
		if(node==null){
			return null;
		}
		Set<Node> dependNodes = node.getDependencies();
		if(dependNodes==null){
			return null;
		}
		List<BundleMeta> list=new ArrayList<BundleMeta>();
		for(Description description:dependNodes){
			if(description instanceof Node){
				Node depend=(Node) description;
				list.add(new BundleMetaImpl(depend));
			}
		}
		return list;
	}
	
	@Override
	public List<Bundle> installBundle(BundleMeta meta,BundleContext bundleContext){
		List<Bundle> list=new ArrayList<Bundle>();
		List<Bundle> dependencies=_installDependencies(meta,bundleContext);
		if(dependencies!=null){
			list.addAll(dependencies);
		}
		Bundle bundle=getBundle(meta,bundleContext);
		if(bundle==null){
			try {
				bundle=bundleContext.installBundle(meta.getPath());
				list.add(bundle);
			} catch (BundleException e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	private List<Bundle> _installDependencies(BundleMeta meta, BundleContext bundleContext) {
		List<BundleMeta> depends = getDependencies(meta);
		if(depends==null){
			return null;
		}
		List<Bundle> list=new ArrayList<Bundle>();
		for(BundleMeta depend:depends){
			List<Bundle> installed = installBundle(depend, bundleContext);
			if(installed!=null){
				list.addAll(installed);
			}
		}
		return list;
	}

	private Bundle getBundle(BundleMeta meta, BundleContext bundleContext) {
		for(Bundle bundle:bundleContext.getBundles()){
			if(meta.getSymbolicName().equals(bundle.getSymbolicName())&&bundle.getVersion().toString().equals(meta.getVersion())){
				return bundle;
			}
		}
		return null;
	}

	@Override
	public void startBundle(String name, String version) {
		BundleMeta meta = getBundle(name, version);
		if(meta==null){
			return;
		}
		String path = meta.getPath();
		try {
			Bundle bundle = Activator.getContext().installBundle(path);
			BundleUtil.startBundle(bundle);
		} catch (BundleException e) {
			e.printStackTrace();
		}
	}

}
